3 * Block restriction interface.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
23 namespace MediaWiki\Block
;
25 use MediaWiki\Block\Restriction\NamespaceRestriction
;
26 use MediaWiki\Block\Restriction\PageRestriction
;
27 use MediaWiki\Block\Restriction\Restriction
;
28 use Wikimedia\Rdbms\IResultWrapper
;
29 use Wikimedia\Rdbms\IDatabase
;
31 class BlockRestriction
{
34 * Map of all of the restriction types.
36 private static $types = [
37 PageRestriction
::TYPE_ID
=> PageRestriction
::class,
38 NamespaceRestriction
::TYPE_ID
=> NamespaceRestriction
::class,
42 * Retrieves the restrictions from the database by block id.
45 * @param int|array $blockId
46 * @param IDatabase|null $db
47 * @return Restriction[]
49 public static function loadByBlockId( $blockId, IDatabase
$db = null ) {
50 if ( $blockId === null ||
$blockId === [] ) {
54 $db = $db ?
: wfGetDB( DB_REPLICA
);
56 $result = $db->select(
57 [ 'ipblocks_restrictions', 'page' ],
58 [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ],
59 [ 'ir_ipb_id' => $blockId ],
62 [ 'page' => [ 'LEFT JOIN', [ 'ir_type' => PageRestriction
::TYPE_ID
, 'ir_value=page_id' ] ] ]
65 return self
::resultToRestrictions( $result );
69 * Inserts the restrictions into the database.
72 * @param Restriction[] $restrictions
75 public static function insert( array $restrictions ) {
76 if ( !$restrictions ) {
81 foreach ( $restrictions as $restriction ) {
82 if ( !$restriction instanceof Restriction
) {
85 $rows[] = $restriction->toRow();
92 $dbw = wfGetDB( DB_MASTER
);
95 'ipblocks_restrictions',
105 * Updates the list of restrictions. This method does not allow removing all
106 * of the restrictions. To do that, use ::deleteByBlockId().
109 * @param Restriction[] $restrictions
112 public static function update( array $restrictions ) {
113 $dbw = wfGetDB( DB_MASTER
);
115 $dbw->startAtomic( __METHOD__
);
117 // Organize the restrictions by blockid.
118 $restrictionList = self
::restrictionsByBlockId( $restrictions );
120 // Load the existing restrictions and organize by block id. Any block ids
121 // that were passed into this function will be used to load all of the
122 // existing restrictions. This list might be the same, or may be completely
125 $blockIds = array_keys( $restrictionList );
126 if ( !empty( $blockIds ) ) {
127 $result = $dbw->select(
128 [ 'ipblocks_restrictions' ],
129 [ 'ir_ipb_id', 'ir_type', 'ir_value' ],
130 [ 'ir_ipb_id' => $blockIds ],
135 $existingList = self
::restrictionsByBlockId(
136 self
::resultToRestrictions( $result )
141 // Perform the actions on a per block-id basis.
142 foreach ( $restrictionList as $blockId => $blockRestrictions ) {
143 // Insert all of the restrictions first, ignoring ones that already exist.
144 $success = self
::insert( $blockRestrictions );
146 // Update the result. The first false is the result, otherwise, true.
147 $result = $success && $result;
149 $restrictionsToRemove = self
::restrictionsToRemove(
150 $existingList[$blockId] ??
[],
154 if ( empty( $restrictionsToRemove ) ) {
158 $success = self
::delete( $restrictionsToRemove );
160 // Update the result. The first false is the result, otherwise, true.
161 $result = $success && $result;
164 $dbw->endAtomic( __METHOD__
);
170 * Updates the list of restrictions by parent id.
173 * @param int $parentBlockId
174 * @param Restriction[] $restrictions
177 public static function updateByParentBlockId( $parentBlockId, array $restrictions ) {
178 // If removing all of the restrictions, then just delete them all.
179 if ( empty( $restrictions ) ) {
180 return self
::deleteByParentBlockId( $parentBlockId );
183 $parentBlockId = (int)$parentBlockId;
185 $db = wfGetDB( DB_MASTER
);
187 $db->startAtomic( __METHOD__
);
189 $blockIds = $db->selectFieldValues(
192 [ 'ipb_parent_block_id' => $parentBlockId ],
198 foreach ( $blockIds as $id ) {
199 $success = self
::update( self
::setBlockId( $id, $restrictions ) );
200 // Update the result. The first false is the result, otherwise, true.
201 $result = $success && $result;
204 $db->endAtomic( __METHOD__
);
210 * Delete the restrictions.
213 * @param Restriction[]|null $restrictions
214 * @throws MWException
217 public static function delete( array $restrictions ) {
218 $dbw = wfGetDB( DB_MASTER
);
220 foreach ( $restrictions as $restriction ) {
221 if ( !$restriction instanceof Restriction
) {
225 $success = $dbw->delete(
226 'ipblocks_restrictions',
227 // The restriction row is made up of a compound primary key. Therefore,
228 // the row and the delete conditions are the same.
229 $restriction->toRow(),
232 // Update the result. The first false is the result, otherwise, true.
233 $result = $success && $result;
240 * Delete the restrictions by Block ID.
243 * @param int|array $blockId
244 * @throws MWException
247 public static function deleteByBlockId( $blockId ) {
248 $dbw = wfGetDB( DB_MASTER
);
250 'ipblocks_restrictions',
251 [ 'ir_ipb_id' => $blockId ],
257 * Delete the restrictions by Parent Block ID.
260 * @param int|array $parentBlockId
261 * @throws MWException
264 public static function deleteByParentBlockId( $parentBlockId ) {
265 $dbw = wfGetDB( DB_MASTER
);
266 return $dbw->deleteJoin(
267 'ipblocks_restrictions',
271 [ 'ipb_parent_block_id' => $parentBlockId ],
277 * Checks if two arrays of Restrictions are effectively equal. This is a loose
278 * equality check as the restrictions do not have to contain the same block
282 * @param Restriction[] $a
283 * @param Restriction[] $b
286 public static function equals( array $a, array $b ) {
287 $filter = function ( $restriction ) {
288 return $restriction instanceof Restriction
;
291 // Ensure that every item in the array is a Restriction. This prevents a
292 // fatal error from calling Restriction::getHash if something in the array
293 // is not a restriction.
294 $a = array_filter( $a, $filter );
295 $b = array_filter( $b, $filter );
297 $aCount = count( $a );
298 $bCount = count( $b );
300 // If the count is different, then they are obviously a different set.
301 if ( $aCount !== $bCount ) {
305 // If both sets contain no items, then they are the same set.
306 if ( $aCount === 0 && $bCount === 0 ) {
310 $hasher = function ( $r ) {
311 return $r->getHash();
314 $aHashes = array_map( $hasher, $a );
315 $bHashes = array_map( $hasher, $b );
320 return $aHashes === $bHashes;
324 * Set the blockId on a set of restrictions and return a new set.
327 * @param int $blockId
328 * @param Restriction[] $restrictions
329 * @return Restriction[]
331 public static function setBlockId( $blockId, array $restrictions ) {
332 $blockRestrictions = [];
334 foreach ( $restrictions as $restriction ) {
335 if ( !$restriction instanceof Restriction
) {
339 // Clone the restriction so any references to the current restriction are
340 // not suddenly changed to a different blockId.
341 $restriction = clone $restriction;
342 $restriction->setBlockId( $blockId );
344 $blockRestrictions[] = $restriction;
347 return $blockRestrictions;
351 * Get the restrictions that should be removed, which are existing
352 * restrictions that are not in the new list of restrictions.
354 * @param Restriction[] $existing
355 * @param Restriction[] $new
358 private static function restrictionsToRemove( array $existing, array $new ) {
359 return array_filter( $existing, function ( $e ) use ( $new ) {
360 foreach ( $new as $restriction ) {
361 if ( !$restriction instanceof Restriction
) {
365 if ( $restriction->equals( $e ) ) {
375 * Converts an array of restrictions to an associative array of restrictions
376 * where the keys are the block ids.
378 * @param Restriction[] $restrictions
381 private static function restrictionsByBlockId( array $restrictions ) {
382 $blockRestrictions = [];
384 foreach ( $restrictions as $restriction ) {
385 // Ensure that all of the items in the array are restrictions.
386 if ( !$restriction instanceof Restriction
) {
390 if ( !isset( $blockRestrictions[$restriction->getBlockId()] ) ) {
391 $blockRestrictions[$restriction->getBlockId()] = [];
394 $blockRestrictions[$restriction->getBlockId()][] = $restriction;
397 return $blockRestrictions;
401 * Convert an Result Wrapper to an array of restrictions.
403 * @param IResultWrapper $result
404 * @return Restriction[]
406 private static function resultToRestrictions( IResultWrapper
$result ) {
408 foreach ( $result as $row ) {
409 $restriction = self
::rowToRestriction( $row );
411 if ( !$restriction ) {
415 $restrictions[] = $restriction;
418 return $restrictions;
422 * Convert a result row from the database into a restriction object.
424 * @param \stdClass $row
425 * @return Restriction|null
427 private static function rowToRestriction( \stdClass
$row ) {
428 if ( array_key_exists( (int)$row->ir_type
, self
::$types ) ) {
429 $class = self
::$types[ (int)$row->ir_type
];
430 return call_user_func( [ $class, 'newFromRow' ], $row );